﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.ComponentModel;
using System.Diagnostics;

namespace ResourceExtension
{
    public abstract class ResourcesProvider<TOptions, TBehavior> : INotifyPropertyChanged
        where TOptions : IResourceOptions, new()
        where TBehavior : IResourceBehavior<TOptions>, new()
    {
        public TOptions Options { get; private set; }

        protected TBehavior Behavior { get; private set; }
        
        Dictionary<ResourceXManager, PropertyInfo> managers =
            new Dictionary<ResourceXManager, PropertyInfo>();

        public ResourcesProvider()
        {
            Options = new TOptions();
            Behavior = new TBehavior();
            Options.PropertyChanged += (Options_PropertyChanged);
            Behavior.UpdateEvent += (Behavior_UpdateEvent);

            foreach (PropertyInfo propertyInfo in GetType().GetProperties())
            {
                object[] attributes = propertyInfo.GetCustomAttributes(typeof (ResourcePropertyAttribute), false);

                if (attributes != null && attributes.Length > 0)
                {
                    Type resourceType = propertyInfo.PropertyType;
                    object resourceInstance = propertyInfo.GetValue(this, null);
                    
                    if (resourceInstance == null)
                    {
                        if (Debugger.IsAttached)
                            throw new Exception(string.Format("Property of type: {0} is null. Please, create property getter before call constructor.", resourceType));
                        break;
                    }

                    PropertyInfo managerProperty = resourceInstance.GetType().GetProperty("ResourceManager",
                                                                                          BindingFlags.Static |
                                                                                          BindingFlags.Public);

                    if (managerProperty == null)
                    {
                        if (Debugger.IsAttached)
                            throw new Exception(string.Format("Property ResourceManager of {0} is absent.", resourceType));
                        
                        break;
                    }

                    ResourceXManager resourceXManager = managerProperty.GetValue(null, null) as ResourceXManager;

                    if (resourceXManager == null)
                    {
                        if (Debugger.IsAttached)
                            throw new Exception(string.Format("Property ResourceManager is not ResourceXManager type of {0}.", resourceType));

                        break;
                    }

                    managers[resourceXManager] = propertyInfo;
                }
            }
        }
       
        void Options_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            foreach (KeyValuePair<ResourceXManager, PropertyInfo> pair in managers)
            {
                Behavior.UpdateQuery(pair.Key, pair.Value.PropertyType, Options);
            }
            
        }

        void Behavior_UpdateEvent(object sender, BehaviorUpdateArgs e)
        {
            PropertyInfo propertyInfo;
            if (managers.TryGetValue(e.Manager, out propertyInfo))
            {
                OnPropertyChanged(propertyInfo.Name);
            }
        }

        private void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        #region Implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        private void CreateInstance(PropertyInfo propertyInfo)
        {
            Type resourceType = propertyInfo.PropertyType;

            ConstructorInfo[] info =
                resourceType.GetConstructors(BindingFlags.CreateInstance |
                                                          BindingFlags.Public |
                                                          BindingFlags.Instance);

            if (info == null || info.Length == 0)
            {
                if (Debugger.IsAttached)
                    throw new Exception(string.Format("Not found public constructor of type: {0}.", resourceType));
            }

            object instance = null;

            try
            {
                instance = info[0].Invoke(null);
            }
            catch (Exception exception)
            {
                if (Debugger.IsAttached)
                    throw new Exception(
                        string.Format("When tried to create instance of type {0} exception has appeared.",
                                      resourceType), exception);
            }

            PropertyInfo managerProperty =
                instance.GetType().GetProperty("ResourceManager",
                                               BindingFlags.Static | BindingFlags.Public |
                                               BindingFlags.SetProperty);

            if (managerProperty == null)
            {
                if (Debugger.IsAttached)
                    throw new Exception(string.Format("Not found public setter for ResourceManager of type: {0}.", resourceType));
            }

            ResourceXManager resourceXManager = new ResourceXManager(instance.GetType());

            try
            {
                managerProperty.SetValue(null, resourceXManager, null);
            }
            catch (Exception exception)
            {
                if (Debugger.IsAttached)
                    throw new Exception(
                        string.Format(
                            "When tried to set value for ResourceManager of type {0} exception has appeared.",
                            resourceType), exception);
            }

            managers[resourceXManager] = propertyInfo;

            propertyInfo.SetValue(this, instance, null);
        }
    }

    [AttributeUsage(AttributeTargets.Property)]
    public class ResourcePropertyAttribute : Attribute
    { }
}
